/*
 * lookup functionalty using asynchronous getaddrinfo_a() system call
 */

#include <stdio.h>
#include <sys/epoll.h>
#include <sys/signalfd.h>
#include <signal.h>
#include <assert.h>
#include "lookuputils.h"

#define MAXEVENTS	10

static const char *progname;
static int gotsig=0;
static int count=0;

struct gaicb_fd {
    struct gaicb *host;
    int fd;
};

static void
usage_alookup_client(const int exit_code)
{
    fprintf(stderr, "%s: -s <server address> -p <server port> -f <name to ip lookup> [ -r <ip to name lookup> ] [ -h <help> ]\n", progname);
    exit(exit_code);
}

static void
usage_alookup(const int exit_code)
{
    fprintf(stderr, "%s: -p <port> [ -h <help> ]\n", progname);
    exit(exit_code);
}

static int
setup_signalfd()
{
    int s;
    sigset_t mask;

    sigemptyset(&mask);
    sigaddset(&mask, SIGRTMIN);
    sigaddset(&mask, SIGINT);
    sigprocmask(SIG_BLOCK, &mask, NULL);

    s = signalfd(-1, &mask, 0);

    return s;
}

static void
write_to_client(int fd, const char *buffer)
{
    int n;

    assert (buffer != NULL);
    n = write(fd, buffer, strlen(buffer));
    if (n == -1)
	fprintf(stderr, "write(): fail to write on %d", fd);
}

static struct gaicb_fd *
read_signalfd(int fd)
{
    ssize_t s;
    struct signalfd_siginfo fdsi;
    struct gaicb_fd *cli_reqs;

    s = read(fd, &fdsi, sizeof(struct signalfd_siginfo));
    if (s != sizeof(struct signalfd_siginfo))
	perror("read(): fail for signalfd");

    if (fdsi.ssi_signo == SIGRTMIN) {
	cli_reqs = (struct gaicb_fd *) (fdsi.ssi_ptr);
    } else if (fdsi.ssi_signo == SIGINT) {
	fprintf(stderr, "Server Intrrupted!\n");
	gotsig = SIGINT;
    }

    return cli_reqs;
}

static void
name_to_ip(const char *host, int fd)
{
    struct gaicb_fd *cli_reqs;
    struct sigevent sevp;

    cli_reqs = calloc(1, sizeof(struct gaicb_fd));
    cli_reqs->host = calloc(1, sizeof(struct gaicb)); 
    cli_reqs->host->ar_name = strdup(host);
    cli_reqs->fd = fd;

    sevp.sigev_notify = SIGEV_SIGNAL;
    sevp.sigev_value.sival_ptr = cli_reqs;
    sevp.sigev_signo = SIGRTMIN;

    getaddrinfo_a(GAI_NOWAIT, &(cli_reqs->host), 1, &sevp);
}

static void
server_stuff(int fd)
{
    int n;
    char buffer[BUFFERSIZE];

    memset(buffer, 0, sizeof(buffer));
    assert(buffer != NULL);
    n = read(fd, buffer, BUFFERSIZE);
    if (n == -1) {
	printf("fd: %d\n", fd);
	error_msg("read(): fail to read from client");
    }

    name_to_ip(buffer, fd);
}

static const char *
mybasename(const char *path)
{
    const char *name;

    if(path) {
	name = strrchr(path, '/');
	if (name) {
	    return ++name;
	}
    }
    return path;
}

static int
alookup(int argc, char **argv)
{
    int optch, srv_sock, sfd;
    char *port = NULL;

    struct epoll_event ev = { .events = EPOLLIN, .data.fd = -1 };
    struct epoll_event events[MAXEVENTS];
    int n , nfds, epollfd;

    while ((optch = getopt(argc, argv, "p:h")) != -1) {
	switch (optch) {
	    case 'p':
		port = strdup(optarg);
		break;
	    case 'h':
		usage_alookup(0);
		break;
	    default:
		usage_alookup(1);
		break;
	}
    }

    if (!port)
	usage_alookup(1);

    srv_sock = create_tcp_server_socket(port, 5);
    if (make_socket_nonblocking(srv_sock) == -1)
	fprintf(stderr, "make_socket_nonblocking(): fail to make server socket non blocking\nUsing defgaut blocking mode\n");

    /* start epoll events from here */
    epollfd = epoll_create(MAXEVENTS);
    if (epollfd == -1)
	error_msg("epoll_crearte(): fail in epoll_create");

    /* register server listening socket in epoll event */
    ev.events = EPOLLIN;
    ev.data.fd = srv_sock;
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, srv_sock, &ev) == -1)
	error_msg("epoll_ctl: fail in epoll_ctl for srv_sock");

    /* register signal fd in in epoll events */
    sfd = setup_signalfd();
    if (sfd == -1) {
        fprintf(stderr, "fcntl(): fail to set up signalfd %s: %m", strerror(errno));
	exit(1);
    }

    ev.events = EPOLLIN;
    ev.data.fd = sfd;
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sfd, &ev) == -1)
	error_msg("epoll_ctl: fail in epoll_ctl for sfd");

    /* start daemon mode */
    for (;;) {
	nfds = epoll_wait(epollfd, events, MAXEVENTS, -1);
	if (nfds == -1)
	    error_msg("epoll_wait(): fail in epoll_wait");

	for (n = 0; n < nfds; ++n) {
	    if (events[n].data.fd == srv_sock) {
		int cli_sock;
		struct sockaddr_in cli_addr;
		socklen_t alen = sizeof(struct sockaddr_in);

		cli_sock = accept(srv_sock, (struct sockaddr *) &cli_addr, &alen);
		if (cli_sock == -1) {
		    if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
			break;
		    else {
			perror("accept(): fail to accept connection\n");
			break;
		    }
		}
		
		if (make_socket_nonblocking(cli_sock) == -1)
		    fprintf(stderr, "make_socket_nonblocking(): fail to make socket non blocking\n");

		ev.events = EPOLLIN;
		ev.data.fd = cli_sock;
		if (epoll_ctl(epollfd, EPOLL_CTL_ADD, cli_sock, &ev) == -1) {
		    fprintf(stderr, "epoll_ctl: fail in epoll_ctl for cli_sock %d\n", cli_sock);
		    close(cli_sock);
		}
	    } else if (events[n].data.fd == sfd) {
		struct gaicb_fd *cli_reqs;
		struct gaicb *host;
		struct addrinfo *res;
		struct sockaddr_in *ans;

		cli_reqs = read_signalfd(events[n].data.fd);
		if (gotsig == SIGINT)
		    break;
		host = cli_reqs->host;
		count++;

		if (host->ar_result == NULL) {
		    printf("%d: %s: Name of Service not Found\n", count, host->ar_name);
		    write_to_client(cli_reqs->fd, "Name of Service not Found");
		} else {
		    for (res = host->ar_result; res != NULL; res = res->ai_next) {
			if (res->ai_socktype == SOCK_DGRAM) {
			    ans = (struct sockaddr_in *) res->ai_addr;
			    printf("%d: %s : %s\n", count, host->ar_name, inet_ntoa(ans->sin_addr));
			    write_to_client(cli_reqs->fd, inet_ntoa(ans->sin_addr));
			}
		    }
		}
		close(cli_reqs->fd); 
		free((char *) host->ar_name);
		freeaddrinfo(cli_reqs->host->ar_result);
		free(cli_reqs->host);
		free(cli_reqs);

		/*
		struct gaicb_fd *cli_reqs;
		struct addrinfo *host;
		char hostname[BUFFERSIZE] = "";
		struct sockaddr_in *ans;
		cli_reqs = read_signalfd(events[n].data.fd);
		if (gotsig == SIGINT)
		    break;
		for (host = cli_reqs->host->ar_result; host != NULL; host = host->ai_next) {
		    if (getnameinfo (host->ai_addr, host->ai_addrlen, hostname, BUFFERSIZE, NULL, NI_NUMERICHOST , 0) != 0) {
			fprintf(stderr, "getnameinfo(): %m\n");
			break;
		    }
			
		    if (hostname != NULL) {
			ans = (struct sockaddr_in *) host->ai_addr;
			printf("%d: %s : %s\n", count, cli_reqs->host->ar_name, inet_ntoa(ans->sin_addr));
			write_to_client(cli_reqs->fd, inet_ntoa(ans->sin_addr));
		    }
		}
		close(cli_reqs->fd); 
		free((char *) cli_reqs->host->ar_name);
		freeaddrinfo(cli_reqs->host->ar_result);
		free(cli_reqs->host);
		free(cli_reqs); */
	    } else {
		server_stuff(events[n].data.fd);
	    }
	}
	if (gotsig == SIGINT)
	    break;
    }

    close(srv_sock);
    free(port);
    close(sfd);
    close(epollfd);

    return 0;
}

static int
alookup_client(int argc, char **argv)
{
    int optch;
    int  sockfd, n;
    char buffer[BUFFERSIZE];
    char *host = NULL;
    char *ip = NULL;
    char *serverip = NULL;
    char *port = NULL;
    
    while ((optch = getopt(argc, argv, "f:r:s:p:h")) != -1) {
	switch (optch) {
	    case 'f':
		host = optarg;
		break;
	    case 'r':
		ip = optarg;
		break;
	    case 's':
		serverip = optarg;
		break;
	    case 'p':
		port = optarg;
		break;
	    case 'h':
		usage_alookup_client(0);
		break;
	    default:
		usage_alookup_client(1);
		break;
	}
    }

    if (!serverip || !port || !host)
	usage_alookup_client(1);

    sockfd = create_tcp_socket();
    connect_to_tcp_server(sockfd, serverip, port); 

    /* Send host name to server */
    n = write(sockfd, host, strlen(host));
    if (n == -1)
	error_msg("ERROR: Writing to socket");

    /* Get responce from the server */
    while (1) {
	memset(buffer, 0, BUFFERSIZE);
	n = read(sockfd, buffer, BUFFERSIZE);
	if (n == -1) {
	    close (sockfd);
	    error_msg("ERROR: Reading from server");
	    break;
	} else if (n == 0) {
	    close (sockfd);
	    fprintf(stderr, "Connection closed from server\n");
	    break;
	} else
	    printf("%s: %s\n", host, buffer);
    }

    return 0;
}

int main(int argc, char **argv)
{
    progname = mybasename(argv[0]);

    if (strcmp(progname, "alookup") == 0)
	return alookup(argc, argv);
    else if (strcmp(progname, "alookup_client") == 0)
	return alookup_client(argc, argv);
    else {
	fprintf(stderr, "ERROR: Invalid program name\n");
	exit(1);
    }

    return 0;
}

